﻿using Consul;
using M.Core.RmoteCall.Attributes;
using M.Core.RmoteCall.RpcCall;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Net.Sockets;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Thrift;
using Thrift.Protocols;
using Thrift.Transports.Client;
using static M.Core.ClientEventMgr;
using static M.Core.RmoteCall.RpcCall.RpcServiceInfo;
using static M.Core.ServiceDiscovery;

namespace M.Core.RmoteCall.ApiCall
{
    public class ThriftRpcCallHandler : ICallHandler
    {
        ConcurrentDictionary<int, AppInfoAttribute> infoMap = new ConcurrentDictionary<int, AppInfoAttribute>();

        public object Process(Type type, MethodInfo targetMethod, object[] args)
        {
            AppInfoAttribute appInfo = null;
            if (!infoMap.TryGetValue(targetMethod.MetadataToken, out appInfo))
            {
                appInfo = ((AppInfoAttribute)type.GetCustomAttribute(typeof(AppInfoAttribute)));
                infoMap.TryAdd(targetMethod.MetadataToken, appInfo);
            }

            var hostInfo = ServiceDiscovery.GetInstance().GetRpcHostByRoll(appInfo.Name, appInfo.Version);

            Delegate instance = null;

            RpcServiceInfo callModel = RpcServiceKeeper.GetInstance().GetModel(appInfo.Name, hostInfo);


            var item = callModel.GetOneClient();

            var socketObject = item.GetInnerObject();

            if (!socketObject.trans.IsOpen)
            {
                socketObject.trans.OpenAsync().GetAwaiter().GetResult();
            }

            if (socketObject.map.TryGetValue(type.MetadataToken, out CallClientInfo callClientInfo))
            {
                if (!callClientInfo.map.TryGetValue(targetMethod.MetadataToken, out instance))
                {
                    lock (appInfo)
                    {
                        instance = ExpressionGetter(callClientInfo.Client, targetMethod, args);
                        callClientInfo.map.TryAdd(targetMethod.MetadataToken, instance);
                    }
                }
            }
            else
            {
                lock (appInfo)
                {
                    var rpcName = (RpcNameAttribute)type.GetCustomAttribute(typeof(RpcNameAttribute));
                    var clientFullName = targetMethod.DeclaringType.DeclaringType.FullName;
                    var clientType = Assembly.Load(targetMethod.DeclaringType.DeclaringType.Assembly.FullName).GetType($"{clientFullName}+Client");

                    var client = Activator.CreateInstance(clientType, new TMultiplexedProtocol(new TBinaryProtocol(new TFramedClientTransport(socketObject.trans)), rpcName.Name));

                    callClientInfo = new CallClientInfo();

                    callClientInfo.Client = client;
                    instance = ExpressionGetter(callClientInfo.Client, targetMethod, args);
                    callClientInfo.map.TryAdd(targetMethod.MetadataToken, instance);
                    socketObject.map.TryAdd(type.MetadataToken, callClientInfo);
                }

            }

            var ret = ((dynamic)instance?.DynamicInvoke(args));
            try
            {
                var a = ret.GetAwaiter().GetResult();
                item.Dispose();
                return Task.FromResult(a);
            }
            catch (TApplicationException ex)
            {
                RpcEventMgr.GetInstance().SendErrorEvent(ChangeType.Reconn, item, ex);
                throw ex;
            }
            catch (TProtocolException ex)
            {
                RpcEventMgr.GetInstance().SendErrorEvent(ChangeType.Reconn, item, ex);
                throw ex;
            }
            catch (Exception ex)
            {
                if (ex.InnerException is SocketException)
                {
                    RpcEventMgr.GetInstance().SendErrorEvent(ChangeType.ReBulidModel, item, ex);
                }
                else if (ex.StackTrace.Contains("Thrift.Transports.Client"))
                {
                    RpcEventMgr.GetInstance().SendErrorEvent(ChangeType.Reconn, item, ex);
                }
                throw  ex;
            }

        }

        static Delegate ExpressionGetter(Object obj, MethodInfo targetMethod, object[] args)
        {
            List<ParameterExpression> expressions = new List<ParameterExpression>();

            for (int i = 0; i < targetMethod.GetParameters().Length; i++)
            {
                var item = targetMethod.GetParameters()[i];
                var val = args[i];
                expressions.Add(Expression.Parameter(item.ParameterType, item.Name));
            }

            Expression body = Expression.Call(Expression.Constant(obj), targetMethod, expressions);

            return Expression.Lambda(body, expressions).Compile();
        }

    }
}
